package edu.washington.ling.syntric.io;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

import edu.washington.ling.syntric.struct.error.CollectingExceptionHandler;
import edu.washington.ling.syntric.struct.error.UnreadableLineException;
import edu.washington.ling.syntric.struct.error.UnreadableRepresentationException;
import edu.washington.ling.syntric.struct.tree.Node;
import edu.washington.ling.syntric.struct.tree.Token;
import edu.washington.ling.syntric.struct.tree.Tree;
import edu.washington.ling.syntric.util.TreeUtils;


public class TreeReaderSSF extends TreeReader {

	private static String featStructPatternStr = "\\s*\\<fs\\s*(.+?)\\>";
	private static String intNodePatternStr = "^([0-9\\.]+)\\s+\\(\\(\\s+([a-zA-Z0-9\\-\\']+)(\\s+\\<fs.+?\\>)?(\\s+[^\\s]+)?\\s*";
	private static String simpleTokenPatternStr = "^([0-9\\.]+)\\s+([^\\s]+)\\s*$";
	private static String complexTokenPatternStr = "^([0-9\\.]+)\\s+([^\\s]+)\\s+(\\w+)\\s+(\\<fs.+?\\>)";
	private static String endBranchPatternStr = "^\\s+\\)\\)";
	private static String ssfPatternStr = String.format("%s|%s|%s|%s", intNodePatternStr, simpleTokenPatternStr,
			complexTokenPatternStr, endBranchPatternStr);
	private static Pattern featStructPattern = Pattern.compile(featStructPatternStr);
	private static Pattern intNodePattern = Pattern.compile(intNodePatternStr);
	private static Pattern complexTokenPattern = Pattern.compile(complexTokenPatternStr);
	private static Pattern simpleTokenPattern = Pattern.compile(simpleTokenPatternStr);
	private static Pattern endBranchPattern = Pattern.compile(endBranchPatternStr);
	private static Pattern ssfPattern = Pattern.compile(ssfPatternStr);
	
	private boolean isSSFLine(String line) {
		return ssfPattern.matcher(line).matches();
	}
	private boolean isIntNodeLine(String line) {
		return intNodePattern.matcher(line).matches();
	}
	private boolean isTokenLine(String line) {
		return isComplexTokenLine(line) || isSimpleTokenLine(line);
	}
	private boolean isComplexTokenLine(String line) {
		return complexTokenPattern.matcher(line).matches();
	}
	private boolean isSimpleTokenLine(String line) {
		return simpleTokenPattern.matcher(line).matches();
	}
	private boolean isEndBranchLine(String line) {
		return endBranchPattern.matcher(line).matches();
	}
	private void readFeatureStructure(String fs, Node n) {
		Matcher m = featStructPattern.matcher(fs);
		if (m.matches()) {
			String[] fvStrs = m.group(1).split("\\s+");
			for (String fvStr: fvStrs) {
				String[] fvPair = fvStr.split("=");
				String feat = fvPair[0];
				String val = fvPair[1].replaceAll("[\\'\"]", "");
				n.setFeature(feat, val);
			}
		}
	}
	private Node readIntNodeLine(String line, Node lastNode, Tree tree) {
		Node newNode = new Node();
		Matcher m = intNodePattern.matcher(line);
		if (m.matches()) {
			String label = m.group(2);
			newNode.setLabel(label);
			if (m.group(3) != null) {
				String fs = m.group(3);
				readFeatureStructure(fs, newNode);
			}
			if (m.group(4) != null) {
				String headWordStr = m.group(4).trim();
				newNode.addAllHeadWords(headWordStr.split("|"));
			}
		}
		if (lastNode == null) {
			tree.setPSRoot(newNode);
		}
		else {
			newNode.setPSParent(lastNode);
			lastNode.addPSChild(newNode);
		}
		return newNode;
	}
	
	private void setAsEmptyCategory(Token t, Tree tree) {
		if (t.getTag().equals("EC") || t.hasFeature("ectype")) {
			t.setAsPSEmptyCategory();
		}
		else {
			t.setAsDSDEmptyCategory();
		}
	}
	private Node readTokenLine(String line, Node lastNode, Tree tree) throws UnreadableRepresentationException{
		Token newToken = new Token();
		Matcher complexM = complexTokenPattern.matcher(line);
		Matcher simpleM = simpleTokenPattern.matcher(line);
		if (complexM.matches()) {
			String word = complexM.group(2);
			String tag = complexM.group(3);
			String fs = complexM.group(4);
			newToken.setWord(word);
			newToken.setTag(tag);
			readFeatureStructure(fs, newToken);
			tree.addToken(newToken);
			if (newToken.getWord().equals("NULL") || newToken.hasFeature("dmrel") || newToken.hasFeature("ectype")) {
				setAsEmptyCategory(newToken, tree);
			}
		}
		else if (simpleM.matches()) {
			String word = simpleM.group(2);
			newToken.setWord(word);
			tree.addToken(newToken);
		}
		if (lastNode != null) {
			newToken.setPSParent(lastNode);
			lastNode.addPSChild(newToken);
		}
		return lastNode;
	}
	private void checkLineFormat(String line) throws UnreadableLineException {
		if (!isSSFLine(line)) {
			throw new UnreadableLineException("misformatted ssf line", line, "refer to SSF specification for correct format");
		}
	}
	private Node readSSFLine(String line, Node lastNode, Tree tree) throws UnreadableRepresentationException {
		Node newNode = null;
		if (isIntNodeLine(line)) {
			newNode = readIntNodeLine(line, lastNode, tree);
		}
		else if(isTokenLine(line)) {
			newNode = readTokenLine(line, lastNode, tree);
		}
		else if(isEndBranchLine(line)) {
			if (lastNode.hasPSParent()) {
				newNode = lastNode.getPSParent();
			}
			else { newNode = lastNode; }
		}
		return newNode;
	}
	
	public void checkRepFormat(TreeRepresentation rep) throws UnreadableRepresentationException {
		CollectingExceptionHandler cHandler = new CollectingExceptionHandler();
		for (String line: rep.getTreeLines()) {
			try { checkLineFormat(line); }
			catch(UnreadableLineException e) {
				cHandler.handle(e);
			}
		}
		if(cHandler.hasMessages()) {
			String errMsg = String.format("Tree representation was misformatted.");
			throw new UnreadableRepresentationException(errMsg, cHandler.getMessages());
		}
	}
	@Override
	public Tree readTree(TreeRepresentation tr) throws UnreadableRepresentationException {
		checkRepFormat(tr);
		Tree tree = new Tree(tr.getLayer());
		tree.setMetaData(tr.getMetaData());
		Node lastNode = null;
		for (String line: tr.getTreeLines()) {
			lastNode = readSSFLine(line, lastNode, tree);
		}
		if (tr.getLayer().toUpperCase().startsWith("DS")) {
			TreeUtils.derriveDSLinksFromFeatures(tree);
		}
		if (tr.getLayer().toUpperCase().startsWith("PB")) {
			TreeUtils.derrivePBLinksFromFeatures(tree);
		}
		return tree;
	}
	@Override
	public String getFormat() {
		return "ssf";
	}
	
}
